#include <GL/gl.h>
#include <iostream>

#include "vector.h"
#include "combinedObject.h"
#include "defines/axes.h"
#include "defines/rayState.h"

CombinedObject::CombinedObject(int _id) : Object(_id){
    test = false;
}

CombinedObject::CombinedObject(const CombinedObject& _c, std::vector<Object *> _objs, int _id)
    : Object(_c.getMaterial(), _c.getCenter(), _id, _c.getType(), false, _c.getTransformation()), objects(_objs){
    test = false;
}

CombinedObject::CombinedObject(int _operation, const Material &_m, int _id)
    : Object(_m, _id, _operation, false){
    test = false;
}

CombinedObject::CombinedObject(int _operation, std::vector<Object *> _objects, const Material &_m, int _id, bool _test)
    : Object(_m, _id, _operation, false), objects(_objects), test(_test){
    calculateCenter();
}

CombinedObject::CombinedObject(int _operation, const Vector3D &_center, const Material &_m, const Matrix &_matrix, const Matrix &_matrixI, int _id, bool _test)
    : Object(_m, _center, _id, _operation, false, _matrix, _matrixI), test(_test){
}

CombinedObject::~CombinedObject(){
    if(!test){
        unsigned int size = objects.size();
        for(unsigned int i = 0; i < size; i++){
            Object *obj = objects.back();
            objects.pop_back();
            delete obj;
        }
    }
}

void CombinedObject::changeOperation(int _op){
    type = _op;
}

void CombinedObject::deleteObject(Object *_o){
    int size = objects.size();
    for (int i = size; i > 0; i--){
        if ( objects.at(i - 1) == _o ){
            objects.erase(objects.begin() + i - 1);
        }
    }
}

unsigned int CombinedObject::getObjectsSize() const{
    return objects.size();
}

Object *CombinedObject::getObject(unsigned int _i) const{
    return objects.at(_i);
}

void CombinedObject::addObject(Object *_obj){
    objects.push_back(_obj);
    calculateCenter();
}

void CombinedObject::draw(bool _wireframe, bool _useParentColor, const Material &_parentColor) const{
    if(selected){
        Vector3D min, max;
        getMinMax(&min.x, &max.x, X_AXIS);
        getMinMax(&min.y, &max.y, Y_AXIS);
        getMinMax(&min.z, &max.z, Z_AXIS);
        drawBox(true, min, max);
    }


    glPushMatrix();
	glMultMatrixd(transformation.getLocalToWorld().toGlMatrix());

    for(unsigned int i = 0; i < objects.size(); i++){
        Object *obj = objects.at(i);
        if(_useParentColor){
            obj->draw(_wireframe, _useParentColor, _parentColor);
        }else{
            obj->draw(_wireframe, true, material);
        }
    }
    glPopMatrix();
}

std::vector<Trio> CombinedObject::intersection(const Ray &_ray) const{
//    std::cout <<"Começando interseção" <<std::endl;
//    std::cout <<"RAIO: \n\torigem" <<std::endl;
//    _ray.getOrigin().print();
//    std::cout <<"\n\tdireção" <<std::endl;
//    _ray.getDirection().print();
    std::vector<Trio> result;

    //não ha necessidade de inserir o primeiro porque todos os objetos ja o terão

    //colocnado o raio para coordenadas da primitiva
    Matrix toLocalVector = transformation.getWorldToLocal();
    Matrix toLocalDirection = transformation.getWorldToLocalDirection();

    Vector3D origin = (toLocalVector * _ray.getOrigin().toHomogeneousCoordinates()).fromHomogeneousCoordinates();
    Vector3D direction = Vector3D(toLocalDirection * Vector(_ray.getDirection()));

//    std::cout <<"Pegando a interseção para cada objeto" <<std::endl;
    Ray newRay(origin, direction);
    for(unsigned int i = 0; i < objects.size(); i++){
//        std::cout <<"Objeto = " << objects.at(i)->getId() <<std::endl;
        //para cada objeto da combinação
        std::vector<Trio> resultByObject = objects.at(i)->intersection(newRay);

//        std::cout <<"\tAntes do empty\n\tresult.size() " << result.size() << "\n\tresultByObject.size()" << resultByObject.size()<<std::endl;
        if(result.empty()){
            result = resultByObject;
            continue;
        }

//
//
//        std::cout <<"\n\nLista do objeto " << i <<std::endl;
//        for(unsigned int j = 0; j < resultByObject.size(); j++){
//            std::cout<< j <<"  =  " << resultByObject.at(j).type << "    ";
//            resultByObject.at(j).point.print();
//        }




        //ordena com a outra lista
        unsigned int kResult = 0; // contador para a lista resultado
        unsigned int kObject = 0; // contador para a lista do objeto
        std::vector<Trio> auxResult;

//        std::cout <<"\tAntes do while \n\tresult.size() " << result.size() << "\n\tresultByObject.size()" << resultByObject.size()<<std::endl;
        while( (kResult < result.size() && kObject < resultByObject.size()) ){
            Vector3D pResult = result.at(kResult).point;
            Vector3D pObject = resultByObject.at(kObject).point;
            Vector3D nResult = result.at(kResult).normal;
            Vector3D nObject = resultByObject.at(kObject).normal;
            double tResult = (pResult - origin).length_2();
            double tObject = (pObject - origin).length_2();

//            std::cout <<"kResult = " << kResult << "\nkObject = " << kObject <<std::endl;
//            std::cout <<"tResult = " << tResult << "\ntObject = " << tObject <<std::endl;

//            std::cout <<"Add um ponto" <<std::endl;
            if( fabs(tResult - tObject) < ERROR ){//se os valores são iguais.... sempre o primeiro ponto entra aqui, porque é a origem.

//                std::cout <<"\tIguais" <<std::endl;
//                std::cout <<"IF 1"<<std::endl;
                unsigned int typeResult = result.at(kResult).type;
                unsigned int typeObject = resultByObject.at(kObject).type;
                auxResult.push_back(Trio(pResult, nResult, classify(typeResult, typeObject)));
                kResult++;
                kObject++;
            }else if(tResult < tObject){
//                std::cout <<"\tAdd o original" <<std::endl;
//                std::cout <<"IF 2"<<std::endl;
                unsigned int typeResult = result.at(kResult).type;
                unsigned int typeObject = resultByObject.at(kObject - 1).type;
                auxResult.push_back(Trio(pResult, nResult, classify(typeResult, typeObject)));
                if(type == DIFFERENCE){
                    if( (typeResult != OUT) && (result.at(kResult-1).type != OUT)){//&& (typeObject != OUT) ){
//                        std::cout <<"\tTrocou a normal do primeiro" <<std::endl;
                        auxResult.back().normal = - auxResult.back().normal;
                    }
                }
                kResult++;
            }else{
//                std::cout <<"\tAdd o do outro objeto" <<std::endl;
//                std::cout <<"IF 3    resultsize = " << result.size() << " kResult = " << kResult<<std::endl;
                unsigned int typeResult = result.at(kResult - 1).type;
                unsigned int typeObject = resultByObject.at(kObject).type;
                auxResult.push_back(Trio(pObject, nObject, classify(typeResult, typeObject)));
//                std::cout <<"----IF 3"<<std::endl;
                if(type == DIFFERENCE){
                    if( (typeResult != OUT) && (result.at(kResult-1).type != OUT) ){//&& (typeObject != OUT) ){
//                        std::cout <<"\tTrocou a normal do segundo" <<std::endl;
                        auxResult.back().normal = - auxResult.back().normal;
                    }
                }
//                std::cout <<"----------IF 3"<<std::endl;
                kObject++;
            }
        }

//        std::cout <<"\tDepois do while " <<std::endl;
        //adicionar o que resta na lista que sobrou
        for(; kResult < result.size(); kResult++){
            //sobrou coisa do result
            Vector3D pResult = result.at(kResult).point;
            Vector3D nResult = result.at(kResult).normal;
            unsigned int typeResult = result.at(kResult).type;
            unsigned int typeObject = OUT;
            auxResult.push_back(Trio(pResult, nResult, classify(typeResult, typeObject)));
//            if(type == DIFFERENCE){
//                if( (typeResult != OUT) && (typeObject != OUT) ){
//                    std::cout <<"\tTrocou a normal do primeiro" <<std::endl;
//                    auxResult.back().normal = - auxResult.back().normal;
//                }
//            }
        }
//        bool emptyResult = result.empty();
        for(; kObject < resultByObject.size(); kObject++){
            //sobrou coisa do resultByObject
            Vector3D pObject = resultByObject.at(kObject).point;
            Vector3D nObject = resultByObject.at(kObject).normal;
//            if(emptyResult) auxResult.push_back(resultByObject.at(kObject));
//            else
            unsigned int typeResult = OUT;
            unsigned int typeObject = resultByObject.at(kObject).type;
            auxResult.push_back(Trio(pObject, nObject, classify(typeResult, typeObject)));
//                std::cout <<"----IF 3"<<std::endl;
//            if(type == DIFFERENCE){
//                if( (typeResult != OUT) && (typeObject != OUT)){
//                    std::cout <<"\tTrocou a normal do segundo" <<std::endl;
//                    auxResult.back().normal = - auxResult.back().normal;
//                }
//            }
        }

        result = auxResult;


//        std::cout <<"\tLista parcial objeto " << i <<std::endl;
//        for(unsigned int j = 0; j < result.size(); j++){
//            std::cout << "\t"<< j <<"  =  " << result.at(j).type << "    ";
//            result.at(j).point.print();
//        }
    }

//        std::cout <<"\n\n\n\n\n\n---feito a combinação dos raios" << std::endl;
    if(result.empty()) return result;

    //enxuga a lista, e passa os pontos para a coordenada global
    Matrix toWorld = transformation.getLocalToWorld();
    Matrix toLocalTranpost = transformation.getWorldToLocalDirection().transpose();
    unsigned int k = 1;
    unsigned int previousClassification = result.front().type;

//
//            std::cout <<"\nNormal antes: " <<std::endl;
//            result.front().normal.print();


    result.front().point = (toWorld * (result.front().point).toHomogeneousCoordinates()).fromHomogeneousCoordinates();
    result.front().normal = Vector3D(toLocalTranpost * Vector(result.front().normal)).normalize();

//
//            std::cout <<"Normal depois: " <<std::endl;
//            result.front().normal.print();


    while(k < result.size()){
        if(result.at(k).type == previousClassification){
            result.erase(result.begin() + k);
        }else{
            previousClassification = result.at(k).type;
            result.at(k).point = (toWorld * (result.at(k).point).toHomogeneousCoordinates()).fromHomogeneousCoordinates();
            result.at(k).normal = Vector3D(toLocalTranpost * Vector(result.at(k).normal)).normalize();

            k++;
        }
    }


//    std::cout <<"\n\nLista FINAL" <<std::endl;
//    for(unsigned int j = 0; j < result.size(); j++){
//        std::cout << "\t"<< j <<"  =  " << result.at(j).type << "    ";
//        result.at(j).point.print();
//    }

//        std::cout <<"Fim da interseção " <<std::endl;
    return result;

}

void CombinedObject::getMinMax(double *_min, double *_max, unsigned int _axis) const{
    switch(type){
        case UNION:{
            objects.at(0)->getMinMax(_min, _max, _axis);

            for(unsigned int i = 1; i < objects.size(); i++){
                double min, max;
                objects.at(i)->getMinMax(&min, &max, _axis);
                if(min < *_min) *_min = min;
                if(max > *_max) *_max = max;
            }
            break;
        }
        case INTERSECTION:{
            objects.at(0)->getMinMax(_min, _max, _axis);
            for(unsigned int i = 1; i < objects.size(); i++){
                double min, max;
                objects.at(i)->getMinMax(&min, &max, _axis);
                if(min > *_min) *_min = min;
                if(max < *_max) *_max = max;
            }
            break;
        }
        case DIFFERENCE:{
            objects.at(0)->getMinMax(_min, _max, _axis);
            break;
        }
    }


}

void CombinedObject::calculateCenter(){
    Vector3D aux;
    unsigned int size = objects.size();
    for(unsigned int i = 0; i < size; i++){
        aux = aux + objects.at(i)->getCenter();
    }

    center = aux * (1.0/size);
}

unsigned int CombinedObject::classify(unsigned int _type1, unsigned int _type2) const{
    switch(type){
        case UNION:{
            if(_type1 == IN || _type2 == IN) return IN;
            if(_type1 == ON || _type2 == ON) return ON;
            return OUT;
            break;
        }
        case INTERSECTION:{
            if(_type1 == OUT || _type2 == OUT) return OUT;
            if(_type1 == ON || _type2 == ON) return ON;
            return IN;
            break;
        }
        case DIFFERENCE:{
//            std::cout <<"_type1 = " << _type1 << "\n_type2 = " << _type2 <<std::endl;
//            std::cout << IN << " " << OUT << " " << ON << std::endl;
            if(_type2 == IN) return OUT;
            if(_type1 == OUT) return OUT;
            if(_type1 == ON){
                if(_type2 == OUT) return ON;
                else return OUT;
            }
            if(_type2 == ON) return ON;
            if(_type2 == OUT) return IN;
            break;
        }
    }
    std::cout <<"Tipo da combinação inexistente"<<std::endl;
    return 0;
}
